using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using WMI;

namespace winsw
{
	public class WrapperService : ServiceBase, EventLogger
	{
		private SERVICE_STATUS wrapperServiceStatus;

		private Process process = new Process();

		private ServiceDescriptor descriptor;

		private Dictionary<string, string> envs;

		private bool orderlyShutdown;

		private bool systemShuttingdown;

		public WrapperService()
		{
			descriptor = new ServiceDescriptor();
			base.ServiceName = descriptor.Id;
			base.CanShutdown = true;
			base.CanStop = true;
			base.CanPauseAndContinue = false;
			base.AutoLog = true;
			systemShuttingdown = false;
		}

		private void HandleFileCopies()
		{
			string path = descriptor.BasePath + ".copies";
			if (File.Exists(path))
			{
				try
				{
					using (StreamReader streamReader = new StreamReader(path, Encoding.UTF8))
					{
						string text;
						while ((text = streamReader.ReadLine()) != null)
						{
							LogEvent("Handling copy: " + text);
							string[] array = text.Split('>');
							if (array.Length > 2)
							{
								LogEvent("Too many delimiters in " + text);
							}
							else
							{
								CopyFile(array[0], array[1]);
							}
						}
					}
				}
				finally
				{
					File.Delete(path);
				}
			}
		}

		private void CopyFile(string sourceFileName, string destFileName)
		{
			try
			{
				File.Delete(destFileName);
				File.Move(sourceFileName, destFileName);
			}
			catch (IOException ex)
			{
				LogEvent("Failed to copy :" + sourceFileName + " to " + destFileName + " because " + ex.Message);
			}
		}

		private void StartThread(ThreadStart main)
		{
			new Thread((ThreadStart)delegate
			{
				try
				{
					main();
				}
				catch (Exception exception)
				{
					WriteEvent("Thread failed unexpectedly", exception);
				}
			}).Start();
		}

		private void HandleLogfiles()
		{
			string logDirectory = descriptor.LogDirectory;
			if (!Directory.Exists(logDirectory))
			{
				Directory.CreateDirectory(logDirectory);
			}
			LogHandler logHandler = descriptor.LogHandler;
			logHandler.EventLogger = this;
			logHandler.log(process.StandardOutput.BaseStream, process.StandardError.BaseStream);
		}

		public void LogEvent(string message)
		{
			if (!systemShuttingdown)
			{
				EventLog.WriteEntry(message);
			}
		}

		public void LogEvent(string message, EventLogEntryType type)
		{
			if (!systemShuttingdown)
			{
				EventLog.WriteEntry(message, type);
			}
		}

		private void WriteEvent(Exception exception)
		{
			WriteEvent(exception.Message + "\nStacktrace:" + exception.StackTrace);
		}

		private void WriteEvent(string message, Exception exception)
		{
			WriteEvent(message + "\nMessage:" + exception.Message + "\nStacktrace:" + exception.StackTrace);
		}

		private void WriteEvent(string message)
		{
			string path = Path.Combine(descriptor.LogDirectory, descriptor.BaseName + ".wrapper.log");
			StreamWriter streamWriter = new StreamWriter(path, append: true);
			streamWriter.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " - " + message);
			streamWriter.Flush();
			streamWriter.Close();
		}

		protected override void OnStart(string[] _)
		{
			envs = descriptor.EnvironmentVariables;
			foreach (string key in envs.Keys)
			{
				LogEvent("envar " + key + '=' + envs[key]);
			}
			HandleFileCopies();
			foreach (Download download in descriptor.Downloads)
			{
				LogEvent("Downloading: " + download.From + " to " + download.To);
				try
				{
					download.Perform();
				}
				catch (Exception ex)
				{
					LogEvent("Failed to download " + download.From + " to " + download.To + "\n" + ex.Message);
					WriteEvent("Failed to download " + download.From + " to " + download.To, ex);
				}
			}
			string startarguments = descriptor.Startarguments;
			startarguments = ((startarguments != null) ? (startarguments + " " + descriptor.Arguments) : descriptor.Arguments);
			LogEvent("Starting " + descriptor.Executable + ' ' + startarguments);
			WriteEvent("Starting " + descriptor.Executable + ' ' + startarguments);
			StartProcess(process, startarguments, descriptor.Executable);
			HandleLogfiles();
			process.StandardInput.Close();
		}

		protected override void OnShutdown()
		{
			try
			{
				systemShuttingdown = true;
				StopIt();
			}
			catch (Exception exception)
			{
				WriteEvent("Shutdown exception", exception);
			}
		}

		protected override void OnStop()
		{
			try
			{
				StopIt();
			}
			catch (Exception exception)
			{
				WriteEvent("Stop exception", exception);
			}
		}

		private void StopIt()
		{
			string stoparguments = descriptor.Stoparguments;
			LogEvent("Stopping " + descriptor.Id);
			WriteEvent("Stopping " + descriptor.Id);
			orderlyShutdown = true;
			if (stoparguments == null)
			{
				try
				{
					WriteEvent("ProcessKill " + this.process.Id);
					StopProcessAndChildren(this.process.Id);
				}
				catch (InvalidOperationException)
				{
				}
			}
			else
			{
				SignalShutdownPending();
				stoparguments = stoparguments + " " + descriptor.Arguments;
				Process process = new Process();
				string text = descriptor.StopExecutable;
				if (text == null)
				{
					text = descriptor.Executable;
				}
				StartProcess(process, stoparguments, text);
				WriteEvent("WaitForProcessToExit " + this.process.Id + "+" + process.Id);
				WaitForProcessToExit(this.process);
				WaitForProcessToExit(process);
				SignalShutdownComplete();
			}
			if (systemShuttingdown && descriptor.BeepOnShutdown)
			{
				Console.Beep();
			}
			WriteEvent("Finished " + descriptor.Id);
		}

		private void StopProcessAndChildren(int pid)
		{
			ManagementObjectSearcher managementObjectSearcher = new ManagementObjectSearcher("Select * From Win32_Process Where ParentProcessID=" + pid);
			foreach (ManagementBaseObject item in managementObjectSearcher.Get())
			{
				StopProcessAndChildren(Convert.ToInt32(item["ProcessID"]));
			}
			Process processById = Process.GetProcessById(pid);
			WriteEvent("Send SIGINT " + process.Id);
			if (SigIntHelper.SendSIGINTToProcess(processById, descriptor.StopTimeout))
			{
				WriteEvent("SIGINT to" + process.Id + " successful");
			}
			else
			{
				try
				{
					WriteEvent("SIGINT to " + process.Id + " failed - Killing as fallback");
					processById.Kill();
				}
				catch (ArgumentException)
				{
				}
			}
		}

		private void WaitForProcessToExit(Process process)
		{
			SignalShutdownPending();
			try
			{
				while (!process.WaitForExit(descriptor.SleepTime.Milliseconds))
				{
					SignalShutdownPending();
				}
			}
			catch (InvalidOperationException)
			{
			}
		}

		private void SignalShutdownPending()
		{
			IntPtr serviceHandle = base.ServiceHandle;
			wrapperServiceStatus.checkPoint++;
			wrapperServiceStatus.waitHint = descriptor.WaitHint.Milliseconds;
			wrapperServiceStatus.currentState = 3;
			Advapi32.SetServiceStatus(serviceHandle, ref wrapperServiceStatus);
		}

		private void SignalShutdownComplete()
		{
			IntPtr serviceHandle = base.ServiceHandle;
			wrapperServiceStatus.checkPoint++;
			wrapperServiceStatus.currentState = 1;
			Advapi32.SetServiceStatus(serviceHandle, ref wrapperServiceStatus);
		}

		private void StartProcess(Process process, string arguments, string executable)
		{
			ProcessStartInfo startInfo = process.StartInfo;
			startInfo.FileName = executable;
			startInfo.Arguments = arguments;
			startInfo.WorkingDirectory = descriptor.WorkingDirectory;
			startInfo.CreateNoWindow = false;
			startInfo.UseShellExecute = false;
			startInfo.RedirectStandardInput = true;
			startInfo.RedirectStandardOutput = true;
			startInfo.RedirectStandardError = true;
			foreach (string key in envs.Keys)
			{
				Environment.SetEnvironmentVariable(key, envs[key]);
			}
			process.Start();
			WriteEvent("Started " + process.Id);
			ProcessPriorityClass priority = descriptor.Priority;
			if (priority != ProcessPriorityClass.Normal)
			{
				process.PriorityClass = priority;
			}
			StartThread(delegate
			{
				string text = process.Id + " - " + process.StartInfo.FileName + " " + process.StartInfo.Arguments;
				process.WaitForExit();
				try
				{
					if (orderlyShutdown)
					{
						LogEvent("Child process [" + text + "] terminated with " + process.ExitCode, EventLogEntryType.Information);
					}
					else
					{
						LogEvent("Child process [" + text + "] finished with " + process.ExitCode, EventLogEntryType.Warning);
						if (process.ExitCode == 0)
						{
							SignalShutdownComplete();
						}
						Environment.Exit(process.ExitCode);
					}
				}
				catch (InvalidOperationException ex)
				{
					LogEvent("WaitForExit " + ex.Message);
				}
				try
				{
					process.Dispose();
				}
				catch (InvalidOperationException ex)
				{
					LogEvent("Dispose " + ex.Message);
				}
			});
		}

		public static int Main(string[] args)
		{
			try
			{
				Run(args);
				return 0;
			}
			catch (WmiException ex)
			{
				Console.Error.WriteLine(ex);
				return (int)ex.ErrorCode;
			}
			catch (Exception value)
			{
				Console.Error.WriteLine(value);
				return -1;
			}
		}

		private static void ThrowNoSuchService()
		{
			throw new WmiException(ReturnValue.NoSuchService);
		}

		public static void Run(string[] _args)
		{
			if (_args.Length > 0)
			{
				ServiceDescriptor serviceDescriptor = new ServiceDescriptor();
				Win32Services collection = new WmiRoot().GetCollection<Win32Services>();
				Win32Service win32Service = collection.Select(serviceDescriptor.Id);
				List<string> list = new List<string>(Array.AsReadOnly(_args));
				if (list[0] == "/redirect")
				{
					FileStream fileStream = new FileStream(list[1], FileMode.Create);
					StreamWriter streamWriter = new StreamWriter(fileStream);
					streamWriter.AutoFlush = true;
					Console.SetOut(streamWriter);
					Console.SetError(streamWriter);
					IntPtr handle = fileStream.Handle;
					Kernel32.SetStdHandle(-11, handle);
					Kernel32.SetStdHandle(-12, handle);
					list = list.GetRange(2, list.Count - 2);
				}
				list[0] = list[0].ToLower();
				if (list[0] == "install")
				{
					string startName = null;
					string startPassword = null;
					if (list.Count > 1 && list[1] == "/p")
					{
						Console.Write("Username: ");
						startName = Console.ReadLine();
						Console.Write("Password: ");
						startPassword = ReadPassword();
					}
					else if (serviceDescriptor.HasServiceAccount())
					{
						startName = serviceDescriptor.ServiceAccountUser;
						startPassword = serviceDescriptor.ServiceAccountPassword;
					}
					collection.Create(serviceDescriptor.Id, serviceDescriptor.Caption, "\"" + serviceDescriptor.ExecutablePath + "\"", WMI.ServiceType.OwnProcess, ErrorControl.UserNotified, StartMode.Automatic, serviceDescriptor.Interactive, startName, startPassword, serviceDescriptor.ServiceDependencies);
					Registry.LocalMachine.OpenSubKey("System").OpenSubKey("CurrentControlSet").OpenSubKey("Services")
						.OpenSubKey(serviceDescriptor.Id, writable: true)
						.SetValue("Description", serviceDescriptor.Description);
					List<SC_ACTION> failureActions = serviceDescriptor.FailureActions;
					if (failureActions.Count > 0)
					{
						using (ServiceManager serviceManager = new ServiceManager())
						{
							using (Service service = serviceManager.Open(serviceDescriptor.Id))
							{
								service.ChangeConfig(serviceDescriptor.ResetFailureAfter, failureActions);
							}
						}
					}
				}
				if (list[0] == "uninstall")
				{
					if (win32Service == null)
					{
						return;
					}
					try
					{
						win32Service.Delete();
					}
					catch (WmiException ex)
					{
						if (ex.ErrorCode == ReturnValue.ServiceMarkedForDeletion)
						{
							return;
						}
						throw ex;
					}
				}
				if (list[0] == "start")
				{
					if (win32Service == null)
					{
						ThrowNoSuchService();
					}
					win32Service.StartService();
				}
				if (list[0] == "stop")
				{
					if (win32Service == null)
					{
						ThrowNoSuchService();
					}
					win32Service.StopService();
				}
				if (list[0] == "restart")
				{
					if (win32Service == null)
					{
						ThrowNoSuchService();
					}
					if (win32Service.Started)
					{
						win32Service.StopService();
					}
					while (win32Service.Started)
					{
						Thread.Sleep(1000);
						win32Service = collection.Select(serviceDescriptor.Id);
					}
					win32Service.StartService();
				}
				if (list[0] == "restart!")
				{
					STARTUPINFO lpStartupInfo = default(STARTUPINFO);
					PROCESS_INFORMATION lpProcessInformation = default(PROCESS_INFORMATION);
					if (!Kernel32.CreateProcess(null, serviceDescriptor.ExecutablePath + " restart", IntPtr.Zero, IntPtr.Zero, bInheritHandles: false, 512u, IntPtr.Zero, null, ref lpStartupInfo, out lpProcessInformation))
					{
						throw new Exception("Failed to invoke restart: " + Marshal.GetLastWin32Error());
					}
				}
				if (list[0] == "status")
				{
					if (win32Service == null)
					{
						Console.WriteLine("NonExistent");
					}
					else if (win32Service.Started)
					{
						Console.WriteLine("Started");
					}
					else
					{
						Console.WriteLine("Stopped");
					}
				}
				if (list[0] == "test")
				{
					WrapperService wrapperService = new WrapperService();
					wrapperService.OnStart(list.ToArray());
					Thread.Sleep(1000);
					wrapperService.OnStop();
				}
			}
			else
			{
				ServiceBase.Run(new WrapperService());
			}
		}

		private static string ReadPassword()
		{
			StringBuilder stringBuilder = new StringBuilder();
			while (true)
			{
				bool flag = true;
				ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(intercept: true);
				if (consoleKeyInfo.Key == ConsoleKey.Enter)
				{
					break;
				}
				if (consoleKeyInfo.Key == ConsoleKey.Backspace)
				{
					stringBuilder.Remove(stringBuilder.Length - 1, 1);
					Console.Write("\b \b");
				}
				else
				{
					Console.Write('*');
					stringBuilder.Append(consoleKeyInfo.KeyChar);
				}
			}
			return stringBuilder.ToString();
		}
	}
}
